package com.example.fixsys.common.config;

import com.itextpdf.awt.geom.Rectangle2D;
import com.itextpdf.text.*;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.*;
import com.itextpdf.text.pdf.parser.ImageRenderInfo;
import com.itextpdf.text.pdf.parser.PdfReaderContentParser;
import com.itextpdf.text.pdf.parser.RenderListener;
import com.itextpdf.text.pdf.parser.TextRenderInfo;
import lombok.extern.slf4j.Slf4j;

import javax.swing.*;
import java.awt.*;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * <p>
 * PDF 写入器
 * </p>
 *
 * @Author REID
 * @Blog <a href="https://blog.csdn.net/qq_39035773">Blog</a>
 * @GitHub <a href="https://github.com/BeginnerA">GitHub</a>
 * @Version V1.0
 **/
@Slf4j
public class PdfBaseWriter extends Document {

    //------------------------------------默认参数------------------------------------
    /**
     * 中文字体
     */
    public final static String FONT_NAME_ST_SONG_LIGHT = "STSong-Light";
    /**
     * 中文字体编码
     */
    public final static String FONT_ENCODING_UNI_GB_UCS2_H = "UniGB-UCS2-H";

    //------------------------------------常用字体/颜色------------------------------------

    /**
     * 灰色
     */
    public final static BaseColor GREY_COLOR = new BaseColor(139, 134, 130);
    /**
     * 浅黄色
     */
    public final static BaseColor LIGHT_YELLOW_COLOR = new BaseColor(255, 255, 153);
    /**
     * 浅蓝色
     */
    public final static BaseColor LIGHT_BLUE_COLOR = new BaseColor(133, 188, 224);
    /**
     * 深蓝色
     */
    public final static BaseColor HARD_BLUE_COLOR = new BaseColor(31, 86, 112);

    /**
     * 写出 PDF 文件路径
     */
    private String destFilePath;

    /**
     * 创建页面大小为 A4 的 PDF {@link Document}
     * @param destFilePath PDF 存放路径
     * @throws DocumentException 创建 PDF　流失败
     */
    public PdfBaseWriter(String destFilePath) throws DocumentException {
        super();
        this.destFilePath = destFilePath;
        PdfWriter.getInstance(this, getPdfOutputStream(destFilePath));
    }

    /**
     * 创建 PDF {@link Document}
     * @param destFilePath PDF 存放路径
     * @param PageSize 页面大小 {@link Rectangle}
     * @throws DocumentException　创建 PDF　流失败
     */
    public PdfBaseWriter(String destFilePath, Rectangle PageSize) throws DocumentException {
        super(PageSize);
        this.destFilePath = destFilePath;
        PdfWriter.getInstance(this, getPdfOutputStream(destFilePath));
    }

    //------------------------------------表------------------------------------

    /**
     * 写入表格，表格数据集合中第一行数据 key 为表格表头
     * @param rows 表格数据集合
     * @return 表格 {@link PdfPTable}
     */
    public PdfPTable writeTable(ArrayList<LinkedHashMap<String, Object>> rows) {
        if (rows == null) {
            return null;
        }
        // 根据写入数据宽度创建表格
        List<String> headers = new ArrayList<String>(rows.get(0).keySet());
        PdfPTable table = createWithHeaderTable(headers);

        for (LinkedHashMap<String, Object> row : rows) {
            for (String k : headers) {
                String text = String.valueOf(row.get(k));
                writeCell(String.valueOf(text), calculateColumnNumber(k), 1, table);
            }
        }
        addContent(table);
        return table;
    }

    /**
     * 创建带表头的表格 {@link PdfPTable}
     * @param headers 表头数据集
     * @return 表格 {@link PdfPTable}
     */
    public PdfPTable createWithHeaderTable(List<String> headers) {
        int numColumns = calculateColumnNumber(headers);
        PdfPTable headerTable = new PdfPTable(numColumns);
        headerTable.setWidthPercentage(100);
        headerTable.setSpacingBefore(10);

        for (String text : headers) {
            writeCell(text, Element.ALIGN_CENTER, calculateColumnNumber(text), 1, headerTable, defaultFont(), 0f, Rectangle.BOX, LIGHT_BLUE_COLOR);
        }
        return headerTable;
    }

    /**
     * 设置空白行
     * @param table 表 {@link PdfPTable}
     * @param bgColor 背景颜色 {@link BaseColor}
     * @param columnNumber 列号
     * @param fixedHeight 固定高度
     */
    public void setBlankRow(PdfPTable table, BaseColor bgColor, int columnNumber, float fixedHeight) {
        Paragraph paragraph = new Paragraph("");
        PdfPCell pdfPCell = newPdfPCell(bgColor, 0, 1, columnNumber, 1, paragraph);
        // 单元格固定高度
        pdfPCell.setFixedHeight(fixedHeight);
        table.addCell(pdfPCell);
    }

    //------------------------------------单元格------------------------------------

    /**
     * 写入无边框单元格 {@link PdfPCell}
     * @param text 内容
     * @param align 对齐方式 {@link Element}
     * @param colspan 所占列数
     * @param rowspan 所占行数
     * @param table 表 {@link PdfPTable}
     * @return 单元格 {@link PdfPCell}
     */
    public PdfPCell writeNoBorderCell(String text, int align, int colspan, int rowspan, PdfPTable table) {
        return writeCell(text, align, colspan, rowspan, table, defaultFont(), 0f, Rectangle.NO_BORDER, BaseColor.WHITE);
    }

    /**
     * 写入单元格 {@link PdfPCell}
     * @param text 内容
     * @param colspan 所占列数
     * @param rowspan 所占行数
     * @param table 表 {@link PdfPTable}
     * @return 单元格 {@link PdfPCell}
     */
    public PdfPCell writeCell(String text, int colspan, int rowspan, PdfPTable table) {
        return writeCell(text, Element.ALIGN_CENTER, colspan, rowspan, table, defaultFont(), 0f, Rectangle.BOX, BaseColor.WHITE);
    }

    /**
     * 写入单元格 {@link PdfPCell}
     * @param text 内容
     * @param align 对齐方式 {@link Element}
     * @param colspan 所占列数
     * @param rowspan 所占行数
     * @param table 表 {@link PdfPTable}
     * @param font 字体 {@link Font}
     * @return 单元格 {@link PdfPCell}
     */
    public PdfPCell writeCell(String text, int align, int colspan, int rowspan, PdfPTable table, Font font) {
        return writeCell(text, align, colspan, rowspan, table, font, 0f, Rectangle.BOX);
    }

    /**
     * 写入单元格 {@link PdfPCell}
     * @param text 内容
     * @param align 对齐方式 {@link Element}
     * @param colspan 所占列数
     * @param rowspan 所占行数
     * @param table 表 {@link PdfPTable}
     * @param font 字体 {@link Font}
     * @param paddingLeft 左边距
     * @param border 边框 {@link Rectangle}
     * @return 单元格 {@link PdfPCell}
     */
    public PdfPCell writeCell(String text, int align, int colspan, int rowspan, PdfPTable table, Font font, Float paddingLeft, int border) {
        return writeCell(text, align, colspan, rowspan, table, font, paddingLeft, border, true, BaseColor.WHITE);
    }

    /**
     * 写入单元格 {@link PdfPCell}
     * @param text 内容
     * @param align 对齐方式 {@link Element}
     * @param colspan 所占列数
     * @param rowspan 所占行数
     * @param table 表 {@link PdfPTable}
     * @param font 字体 {@link Font}
     * @param paddingLeft 左边距
     * @param border 边框 {@link Rectangle}
     * @param bgColor 背景颜色 {@link BaseColor}
     * @return 单元格 {@link PdfPCell}
     */
    public PdfPCell writeCell(String text, int align, int colspan, int rowspan, PdfPTable table, Font font, Float paddingLeft, int border, BaseColor bgColor) {
        return writeCell(text, align, colspan, rowspan, table, font, paddingLeft, border, true, bgColor);
    }

    /**
     * 写入单元格 {@link PdfPCell}
     * @param text 内容
     * @param align 对齐方式 {@link Element}
     * @param colspan 所占列数
     * @param rowspan 所占行数
     * @param table 表 {@link PdfPTable}
     * @param font 字体 {@link Font}
     * @param paddingLeft 左边距
     * @param border 边框 {@link Rectangle}
     * @param isSet 是否已经设置
     * @param bgColor 背景颜色 {@link BaseColor}
     * @return 单元格 {@link PdfPCell}
     */
    public PdfPCell writeCell(String text, int align, int colspan, int rowspan, PdfPTable table, Font font, Float paddingLeft, int border, boolean isSet, BaseColor bgColor) {
        if (text == null) {
            text = "";
        }

        // 包含中文，则使用中文字体
        if (isChinese(text)) {
            font = chineseFont(font.getSize(), font.getStyle(), font.getColor());
        }
        Paragraph elements = new Paragraph(text, font);
        elements.setAlignment(align);

        PdfPCell cell = newPdfPCell(bgColor, border, align, colspan, rowspan, elements);
        if (paddingLeft != null) {
            cell.setPaddingLeft(paddingLeft);
        }
        if (isSet) {
            table.addCell(cell);
        }
        return cell;
    }

    /**
     * 创建新的单元格 {@link PdfPCell}
     * @param bgColor 背景颜色 {@link BaseColor}
     * @param border 边框 {@link Rectangle}
     * @param align 对齐方式 {@link Element}
     * @param colspan 所占列数
     * @param rowspan 所占行数
     * @param paragraph 段落 {@link Paragraph}
     * @return 单元格 {@link PdfPCell}
     */
    public PdfPCell newPdfPCell(BaseColor bgColor, int border, int align, int colspan, int rowspan, Paragraph paragraph) {
        PdfPCell cell = new PdfPCell();
        cell.setBorderColor(BaseColor.BLACK);
        cell.setBorderColorLeft(BaseColor.BLACK);
        cell.setBorderColorRight(BaseColor.BLACK);
        cell.setBackgroundColor(bgColor);
        cell.setBorder(border);
        // 水平居中
        cell.setHorizontalAlignment(align);
        // 垂直居中
        cell.setVerticalAlignment(Element.ALIGN_MIDDLE);
        cell.setColspan(colspan);
        cell.setRowspan(rowspan);
        cell.setMinimumHeight(10);
        cell.addElement(paragraph);
        // 设置单元格的边框宽度和颜色
        cell.setBorderWidth(0.5f);
        cell.setBorderColor(GREY_COLOR);
        return cell;
    }

    /**
     * 创建内容为图片的单元格 {@link PdfPCell}
     * @param bgColor 背景 {@link BaseColor}
     * @param border 边框 {@link Rectangle}
     * @param align 对齐方式 {@link Element}
     * @param colspan 所占列数
     * @param rowspan 所占行数
     * @param image 内容(文字或图片对象) {@link Image}
     * @return 单元格 {@link PdfPCell}
     */
    public PdfPCell newPdfPCellOfImage(BaseColor bgColor, int border, int align, int colspan, int rowspan, Image image) {
        PdfPCell cell = new PdfPCell();
        cell.setBorderColor(BaseColor.BLACK);
        cell.setBorderColorLeft(BaseColor.BLACK);
        cell.setBorderColorRight(BaseColor.BLACK);
        cell.setBackgroundColor(bgColor);
        cell.setBorder(border);
        cell.setUseAscender(Boolean.TRUE);
        cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);
        cell.setHorizontalAlignment(align);
        cell.setColspan(colspan);
        cell.setRowspan(rowspan);
        cell.addElement(image);
        return cell;
    }

    //------------------------------------文本内容------------------------------------

    /**
     * 设置一级标题
     * @param title 章节标题
     * @param number 章节编号
     * @return 返回当前标题 {@link Chapter}
     */
    public Chapter setTitle1(String title, int number) {
        Paragraph paragraph = getParagraph(2, title);
        Chapter chapter = new Chapter(paragraph, number);
        chapter.setTriggerNewPage(false);
        addContent(chapter);
        return chapter;
    }

    /**
     * 设置二级标题
     * @param chapter 对应一级标题 {@link Chapter}
     * @param title 章节标题
     * @return 返回当前标题 {@link Section}
     */
    public Section setTitle2(Chapter chapter, String title) {
        Paragraph paragraph = getParagraph(3, title);
        Section section = chapter.addSection(paragraph);
        section.setNumberStyle(Section.NUMBERSTYLE_DOTTED_WITHOUT_FINAL_DOT);
        addContent(section);
        return section;
    }

    /**
     * 设置三级标题
     * @param section 对应二级标题 {@link Section}
     * @param title 章节标题
     */
    public void setTitle3(Section section, String title) {
        Paragraph paragraph = getParagraph(4, title);
        Section subsection = section.addSection(paragraph);
        subsection.setNumberStyle(Section.NUMBERSTYLE_DOTTED);
        addContent(subsection);
    }

    /**
     * 写出段落正文
     * @param text 正文内容
     */
    public void writeText(String text) {
        writeText(text, defaultFont(), true);
    }

    /**
     * 写出段落正文
     * @param text 正文内容
     * @param font 字体 {@link Font}
     */
    public void writeText(String text, Font font) {
        writeText(text, font, true);
    }

    /**
     * 写出段落正文
     * @param text 正文内容
     * @param font 字体 {@link Font}
     * @param firstLineIndent 首行是否需要缩进
     */
    public void writeText(String text, Font font, boolean firstLineIndent) {
        Paragraph paragraph = new Paragraph(text, font);
        if (firstLineIndent) {
            paragraph.setFirstLineIndent(24);
        }
        addContent(paragraph);
    }

    /**
     * 写出段落正文
     * @param text 正文内容
     * @param font 字体 {@link Font}
     * @param alignment 设置此段落的对齐方式 {@link Element}
     */
    public void writeText(String text, Font font, int alignment) {
        Paragraph paragraph = new Paragraph(text, font);
        paragraph.setAlignment(alignment);
        addContent(paragraph);
    }

    /**
     * 写出列表
     * @param texts 列表内容集合
     * @param wantNumbering 是否需要序号
     */
    public void writeList(List<String> texts, boolean wantNumbering) {
        if (texts == null || texts.size() == 0) {
            return;
        }
        int i = 1;
        for (String text : texts) {
            if (wantNumbering) {
                writeText(i + "、" + text, defaultFont(), false);
            }else {
                writeText(text, defaultFont(), false);
            }
            i++;
        }
    }

    /**
     * 写出列表
     * @param formData 列表内容集合
     * @param tagValue value 值是否需要下划线
     */
    public void writeList(Map<String, String> formData, boolean tagValue) {
        if (formData == null || formData.size() == 0) {
            return;
        }

        for (String v : formData.keySet()) {
            addContent(new Phrase(v + "：", defaultFont()));

            Phrase phrase = new Phrase();
            Chunk chunk = new Chunk(formData.get(v), defaultFont());
            if (tagValue) {
                chunk.setUnderline(0.2f, -2f);
            }
            phrase.add(chunk);

            addContent(phrase);
            addContent(Chunk.NEWLINE);
        }
    }

    /**
     * 创建文本
     * @param type 1-标题 2-标题一 3-标题二 4-标题三 5-正文 6-正文左对齐
     */
    public Paragraph getParagraph(int type, String text) {
        Font font = new Font(defaultFont());
        if (1 == type) {
            font.setSize(22f);
            font.setStyle(Font.BOLD);
        } else if(2 == type) {
            font.setSize(16f);
            font.setStyle(Font.BOLD);
        } else if(3 == type) {
            font.setSize(14f);
            font.setStyle(Font.BOLD);
        } else if(4 == type) {
            font.setSize(14f);
        } else if(5 == type) {
            font.setSize(10.5f);
        } else if(6 == type) {
            font.setSize(10.5f);
        } else {
            font.setSize(10.5f);
        }

        Paragraph paragraph = new Paragraph(text, font);
        if (1 == type) {
            paragraph.setAlignment(Paragraph.ALIGN_CENTER);
            paragraph.setSpacingBefore(10f);
            paragraph.setSpacingAfter(10f);
        } else if(2 == type) {
            paragraph.setAlignment(Element.ALIGN_JUSTIFIED);
            paragraph.setSpacingBefore(2f);
            paragraph.setSpacingAfter(2f);
        } else if(3 == type){
            paragraph.setSpacingBefore(2f);
            paragraph.setSpacingAfter(1f);
        } else if(4 == type){
            paragraph.setSpacingBefore(2f);
            paragraph.setSpacingAfter(2f);
        } else if(5 == type){
            paragraph.setAlignment(Element.ALIGN_JUSTIFIED);
            paragraph.setFirstLineIndent(24);
            paragraph.setSpacingBefore(1f);
            paragraph.setSpacingAfter(1f);
        } else if(6 == type){
            paragraph.setAlignment(Element.ALIGN_LEFT);
            paragraph.setSpacingBefore(1f);
            paragraph.setSpacingAfter(1f);
        }
        return paragraph;
    }

    /**
     * 添加 PDF 内容
     * @param element 要添加的 {@link Element}
     * @return 如果添加了元素，则为true ，否则为false
     */
    public boolean addContent(Element element) {
        try {
            return this.add(element);
        } catch (DocumentException e) {
            log.error("PDF 文档尚未打开或已关闭");
            throw new RuntimeException(e);
        }
    }

    //------------------------------------水印------------------------------------

    /**
     * 水印
     */
    private String watermark;

    /**
     * 印章位置关键字
     */
    private String stampPositionKeyword;

    /**
     * 印章文件路径
     */
    private Image stamp;

    /**
     * 阅读 PDF 文档
     */
    PdfReader reader = null;

    /**
     * 将额外内容应用到 PDF 文档的页面。这个额外的内容可以是 PdfContentByte 中允许的所有对象，包括来自其他 Pdfs 的页面。<br>
     * 原始 PDF 将保留所有交互元素，包括书签、链接和表单域。<br>
     * 也可以更改字段值并将它们展平。可以添加新字段，但不能展平。<br>
     */
    PdfStamper stamper = null;

    /**
     * 设置水印内容
     * @param watermark 水印内容
     */
    public void setWatermark(String watermark) {
        if (watermark != null && watermark.trim().length() > 0) {
            this.watermark = watermark;
        }
    }

    /**
     * 设置印章
     * @param stampPositionKeyword 印章位置关键字，根据关键字会计算出印章所在坐标
     * @param stampFilePath 印章文件路径
     */
    public void setStamp(String stampPositionKeyword, String stampFilePath) {
        try {
            if (stampPositionKeyword != null && stampPositionKeyword.trim().length() > 0) {
                this.stampPositionKeyword = stampPositionKeyword;
                this.stamp = Image.getInstance(stampFilePath);
            }
        } catch (MalformedURLException e) {
            log.error("添加印章失败！错误信息为: ", e);
            this.stampPositionKeyword = null;
            this.stamp = null;
            throw new RuntimeException(e);
        } catch (BadElementException e) {
            log.error("添加印章失败！错误信息为: ", e);
            this.stampPositionKeyword = null;
            this.stamp = null;
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("添加印章失败！错误信息为: ", e);
            this.stampPositionKeyword = null;
            this.stamp = null;
            throw new RuntimeException(e);
        }

    }

    /**
     * 设置印章
     * @param stampPositionKeyword 印章位置关键字，根据关键字会计算出印章所在坐标
     * @param stampFilePath 印章文件路径
     */
    public void setStamp(String stampPositionKeyword, URL stampFilePath) {
        try {
            if (stampPositionKeyword != null && stampPositionKeyword.trim().length() > 0) {
                this.stampPositionKeyword = stampPositionKeyword;
                this.stamp = Image.getInstance(stampFilePath);
            }
        } catch (MalformedURLException e) {
            log.error("添加印章失败！错误信息为: ", e);
            this.stampPositionKeyword = null;
            this.stamp = null;
            throw new RuntimeException(e);
        } catch (BadElementException e) {
            log.error("添加印章失败！错误信息为: ", e);
            this.stampPositionKeyword = null;
            this.stamp = null;
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("添加印章失败！错误信息为: ", e);
            this.stampPositionKeyword = null;
            this.stamp = null;
            throw new RuntimeException(e);
        }

    }

    /**
     * 添加水印
     * @param text 水印文本内容
     * @param opacity 水印字体透明度
     * @param fontsize 水印字体大小
     * @param heightDensity 数值越大每页竖向水印越少
     * @param widthDensity 数值越大每页横向水印越少
     * @param angle 水印倾斜角度（0-360）
     */
    private void setWatermark(String text, float opacity, float fontsize, float heightDensity, float widthDensity, float angle) {

        try {
            PdfGState gs = new PdfGState();
            // 设置图片透明度
            gs.setFillOpacity(opacity);
            // 设置笔触字体不透明度
            gs.setStrokeOpacity(0.1f);
            // 水印字体
            BaseFont font = BaseFont.createFont(FONT_NAME_ST_SONG_LIGHT, FONT_ENCODING_UNI_GB_UCS2_H, BaseFont.EMBEDDED);
            // 页面宽高
            Rectangle pageRect = reader.getPageSizeWithRotation(1);
            float pageHeight = pageRect.getHeight();
            float pageWidth = pageRect.getWidth();
            // 水印内容宽高
            JLabel label = new JLabel();
            FontMetrics metrics;
            label.setText(text);
            metrics = label.getFontMetrics(label.getFont());
            int textH = metrics.getHeight();
            int textW = metrics.stringWidth(label.getText());

            int total = reader.getNumberOfPages();

            for (int i = 1; i <= total; i++) {
                /*
                 * PDF 分为四层，第一层和第四层由低级操作来进行操作，第二层、第三层由高级对象操作(从下往上)
                 * 第一层操作只能使用 PdfWriter.DirectContent 操作，第四层使用 DirectContentUnder 操作,。
                 * 第二层和第三层的 PdfContentByte 是由 IText 内部操作，没有提供 api 接口。
                 */
                PdfContentByte waterMar = stamper.getOverContent(i);
                waterMar.beginText();
                // 设置水印颜色
                waterMar.setColorFill(BaseColor.GRAY);
                waterMar.setGState(gs);

                waterMar.setFontAndSize(font, fontsize);

                // 设置水印对齐方式 水印内容 X坐标 Y坐标 旋转角度
                for (float height = textH; height < pageHeight * 2; height = height + textH * heightDensity) {
                    for (float width = textW; width < pageWidth * 1.5 + textW; width = width + textW * widthDensity) {
                        waterMar.showTextAligned(Element.ALIGN_LEFT, text, width - textW, height - textH, angle);
                    }
                }
                waterMar.endText();
            }
        }  catch (DocumentException e) {
            log.error("设置水印失败！错误信息为: ", e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("设置水印失败！错误信息为: ", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 设置图片水印
     */
    private void setImageWatermark() {
        try {
            List<float[]> keywordCoord = getKeywordCoord();
            for (float[] coord : keywordCoord) {
                PdfContentByte under = stamper.getUnderContent((int)coord[2]);
                stamp.setAbsolutePosition(coord[0], coord[1] - 50);
                under.addImage(stamp);
                // TODO: 2022/5/26 印章功能还没搞定，等有时间在接着搞 
                PdfDiv sealImageDiv = new PdfDiv();
                sealImageDiv.setPosition(PdfDiv.PositionType.ABSOLUTE);
                sealImageDiv.setPaddingLeft(coord[0]);
                sealImageDiv.setPaddingBottom(coord[1] - 50);
                stamp.scaleAbsolute(100f, 100f);
                sealImageDiv.addElement(stamp);
            }
        } catch (DocumentException e) {
            log.error("设置图片水印失败！错误信息为: ", e);
            throw new RuntimeException(e);
        }

    }

    /**
     * 获取印章坐标，可能多个位置需要设置印章
     * @return 印章坐标
     */
    private List<float[]> getKeywordCoord() {
        try {
            List<float[]> coordList = new ArrayList<>();
            int pageNum = reader.getNumberOfPages();
            PdfReaderContentParser pdfReaderContentParser = new PdfReaderContentParser(reader);
            // 下标从1开始
            for (int i = 1; i <= pageNum; i++) {
                int finalI = i;
                pdfReaderContentParser.processContent(i, new RenderListener() {
                    public void renderText(TextRenderInfo textRenderInfo) {
                        String text = textRenderInfo.getText();
                        if (null != text && text.contains(stampPositionKeyword)) {
                            Rectangle2D.Float boundingRectange = textRenderInfo.getBaseline().getBoundingRectange();
                            float[] coord = new float[3];
                            coord[0] = boundingRectange.x;
                            coord[1] = boundingRectange.y;
                            coord[2] = finalI;
                            coordList.add(coord);
                        }
                    }
                    public void renderImage(ImageRenderInfo arg0) {

                    }
                    public void endTextBlock() {

                    }
                    public void beginTextBlock() {

                    }
                });
            }
            return coordList;
        } catch (IOException e) {
            log.error("设置图片水印失败！错误信息为: ", e);
            throw new RuntimeException(e);
        }
    }


    //------------------------------------字体------------------------------------

    /**
     * 默认中文字体<br>
     * 大小：12<br>
     * 样式：正常<br>
     * 颜色：黑色<br>
     * @return 中文字体 {@link Font}
     */
    public Font defaultFont() {
        return chineseFont(10.5f, Font.NORMAL, BaseColor.BLACK);
    }

    /**
     * 默认中文字体
     * @return 中文字体 {@link BaseFont}
     */
    public BaseFont defaultBaseFont() {
        try {
            return BaseFont.createFont(FONT_NAME_ST_SONG_LIGHT, FONT_ENCODING_UNI_GB_UCS2_H, BaseFont.EMBEDDED);
        } catch (DocumentException e) {
            log.error("获取无效字体");
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("无法读取字体文件");
            throw new RuntimeException(e);
        }
    }

    /**
     * 中文字体 {@link Font}
     * @param fontSize 字体大小
     * @param style 字体风格 {@link Font}
     * @param color 字体颜色 {@link BaseColor}
     * @return {@link Font}
     */
    public Font chineseFont(float fontSize, int style, BaseColor color) {
        BaseFont baseFont = null;
        try {
            baseFont = BaseFont.createFont(FONT_NAME_ST_SONG_LIGHT, FONT_ENCODING_UNI_GB_UCS2_H, BaseFont.EMBEDDED);
        } catch (DocumentException e) {
            log.error("获取无效字体");
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("无法读取字体文件");
            throw new RuntimeException(e);
        }
        return createFont(baseFont, fontSize, style, color);
    }

    /**
     * 自定义中文字体 {@link Font}
     * @param fontPath 字体格式文件路径（如：.ttc、.ttf），支持绝对路径或项目静态资源相对路径
     * @param fontSize 字体大小
     * @param style 字体风格 {@link Font}
     * @param color 字体颜色 {@link BaseColor}
     * @return {@link Font}
     */
    public Font customFont(String fontPath, float fontSize, int style, BaseColor color) {
        BaseFont baseFont = null;
        try {
            baseFont = BaseFont.createFont(fontPath, BaseFont.IDENTITY_H, BaseFont.EMBEDDED);
        } catch (DocumentException e) {
            log.error("获取无效字体");
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("无法读取字体文件");
            throw new RuntimeException(e);
        }
        return createFont(baseFont, fontSize, style, color);
    }

    /**
     * 创建字体 {@link Font}
     * @param baseFont 字体 {@link BaseFont}
     * @param fontSize 字体大小
     * @param style 字体风格 {@link Font}
     * @param color 字体颜色 {@link BaseColor}
     * @return {@link Font}
     */
    public Font createFont(BaseFont baseFont, float fontSize, int style, BaseColor color) {
        return new Font(baseFont, fontSize, style, color);
    }

    //------------------------------------其它------------------------------------

    /**
     * 关闭文档。<br>
     * 将所有内容写入正文后，您必须关闭正文。在那之后，任何东西都不能再写入身体了。
     */
    public void close() {
        if (!close) {
            open = false;
            close = true;
        }
        for (DocListener listener : listeners) {
            listener.close();
        }
        // 判断导出 PDF 是否带水印
        if (watermark != null) {
            createPdfStamper(destFilePath, getTargetFilePath());
            setWatermark(watermark, 0.8f, 16, 7, 1, 45);
        }
        if (stamp != null) {
            createPdfStamper(destFilePath, getTargetFilePath());
            setImageWatermark();
        }

        closePdfStamper();
    }

    /**
     * 创建 {@link PdfReader} 和 {@link PdfStamper}
     * @param sourceFilePath 需要添加水印的 PDF 源文件路径
     * @param targetFilePath 添加水印后的 PDF 文件路径
     */
    private void createPdfStamper(String sourceFilePath, String targetFilePath) {
        try {
            if (reader == null || stamper == null) {
                reader = new PdfReader(sourceFilePath);
                stamper = new PdfStamper(reader, new FileOutputStream(targetFilePath));
            }
        } catch (DocumentException e) {
            log.error("实例化阅读器失败！错误信息为: ", e);
            throw new RuntimeException(e);
        } catch (FileNotFoundException e) {
            log.error("实例化阅读器失败！错误信息为: ", e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            log.error("实例化阅读器失败！错误信息为: ", e);
            throw new RuntimeException(e);
        }
    }

    /**
     * 关闭 {@link PdfReader} 和 {@link PdfStamper}
     */
    private void closePdfStamper() {
        if (stamper != null) {
            try {
                stamper.close();
            } catch (DocumentException e) {
                log.error("关闭阅读器失败！错误信息为: ", e);
                e.printStackTrace();
            } catch (IOException e) {
                log.error("关闭阅读器失败！错误信息为: ", e);
                e.printStackTrace();
            }
        }
        if (reader != null) {
            reader.close();
        }
    }

    //------------------------------------私有方法------------------------------------

    /**
     * 获取 PDF 写入流
     * @param destFilePath PDF 存放路径
     * @return PDF 写入流 {@link OutputStream}
     */
    private OutputStream getPdfOutputStream(String destFilePath) {
        try {
            return Files.newOutputStream(Paths.get(destFilePath));
        } catch (IOException e) {
            log.error("PDF文件存在但是是目录而不是常规文件，不存在但无法创建，或者由于任何其他原因无法打开");
            throw new RuntimeException(e);
        }
    }

    /**
     * 根据标题列表计算列表列数
     * @param header 标题列表
     * @return 列表列数
     */
    private int calculateColumnNumber(List<String> header) {
        int numColumns = 0;
        for (String s : header) {
            numColumns += calculateColumnNumber(s);
        }
        return numColumns;
    }

    /**
     * 根据标题计算列表列数
     * @param header 标题列表
     * @return 列表列数
     */
    private int calculateColumnNumber(String header) {
        int numColumns = header.length();
        if (!isChinese(header)) {
            numColumns = numColumns / 2 + (numColumns % 2 != 0 ? 1 : 0);
        }
        return numColumns;
    }

    /**
     * 字符串是否是中文
     * @param str 字符串
     * @return 是否是中文
     */
    private boolean isChinese(String str) {
        Pattern p = Pattern.compile("[\u4e00-\u9fa5]");
        Matcher m = p.matcher(str);
        return m.find();
    }

    /**
     * 获取导出带水印的 PDF 文件路径
     * @return 带水印的 PDF 文件路径
     */
    private String getTargetFilePath() {
        int i = destFilePath.lastIndexOf(".");
        String suffix = destFilePath.substring(i);
        String filePath = destFilePath.substring(0, i);
        destFilePath = filePath + "（水印）" + suffix;
        return destFilePath;
    }

}